<link rel="import" href="../../bower_components/polymer/polymer.html">

<!--
A plumber component which joins a number of input bindings. It outputs a list
consisting of all of the inputs that are not null or undefined.

It can take explicit arguments [in0...in98] or it can pull its arguments from
a dom-repeat template, provided that it is given the property name it is looking
for on the repeated children, and that property has {notify: true}. The repeat
binding involves some magic and may not be totally reliable; ping danmane@ if
something goes wrong.

Example:
  <hydrogen-join
    in1="[[foo1]]"
    in2="[[foo2]]"
    in3="[[foo3]]"
    out="{{foos}}" // equivalent to [foo1].push(foo2).push(foo3)
  ></hydrogen-join>

  <hydrogen-join out="{{foo}}" in-join-property="out">
    <template is="dom-repeat" items="[[foos]]">
        <foo item="[[item]]"></foo> //foo has a property out: {type: Array, notify: true}
    </template>
  </hydrogen-join>

Swapping the inJoinProperty is not currently supported, it will warn if you try.

There's a possible bug in repeat mode if an element is removed from the dom-repeat,
but continues to exist somewhere else, and continues to fire property-changed events.
Then the hydrogen-join will inappropriately record its value, even though it is not
still connected in hydrogen-join's DOM.

@demo
-->
<dom-module id="hydrogen-join">
  <script>
    var declaration = {
      is: 'hydrogen-join',
      properties: {
        out: {type: Array, readOnly: true, notify: true},
        _items: {type: Array, value: function() {return [];}},
        /* Property to pull from dom-repeated child nodes and then pull and join */
        inJoinProperty: {type: String, observer: "_modifyJoinProperty"},
      },
      listeners: {
        "dom-change": "_syncListenersAndState",
      },
      /* If we are in repeat-mode, ensure all event listeners are setup, and pull
       * items out of whatever children currently exist
       */
      _syncListenersAndState: function() {
        if (this.inJoinProperty == null) {
          // this codepath is for pulling properties out of children in a repeat
          return;
        }
        function repeatUpdateFunction(i) {
          return function(e) {
            this._items[i] = e.detail.value;
            // Debounce just in case something else thrashes
            this.debounce("updateOut", this._updateOutput)
          }
        }
        // create a new items array to replace the old one, otherwise old items
        // might never get removed when their corresponding element leaves
        this._items = [];
        // Sadly, we need to bind an update function onto every child node,
        // because the property-changed event does not bubble.
        for (var i=0; i<this.childNodes.length; i++) {
          var child = this.childNodes[i];
          if (child.properties != null && child.properties[this.inJoinProperty] != null) {
            child.addEventListener(this.inJoinProperty + "-changed", repeatUpdateFunction(i).bind(this));
            this._items[i] = child[this.inJoinProperty];
          }
        }
        this._updateOutput();
      },
      _modifyJoinProperty: function(newJoinProperty, oldJoinProperty) {
        if (oldJoinProperty != null) {
          console.warning("Changing the join property may be unsafe. Have fun!");
        }
        this._syncListenersAndState();
      },
      _updateOutput: function() {
        var out = [];
        for (var i=0; i<99; i++) {
          if (this._items[i] != null) {
            out.push(this._items[i]);
          }
        }
        this._setOut(out);
      }
    };

    /* Programatically add properties in0-in98, with associated observers */
    function argsUpdateFunction(i) {
      return function(newval) {
        this._items[i] = newval;
        if (i === 98) {
          console.warn("You're updating the last arg (98). Possibly some values are being lost");
        }
        if (this.inJoinProperty != null) {
          console.warn("It looks like you're providing a join property and also arguments. This is not supported.")
        }
        this.debounce("updateOut", this._updateOutput)
      }
    }
    // I got 99 arguments and ain't off by 1!
    for (var i = 0; i < 99; i++) {
      var propName = "in" + i;
      var updateName = "_update" + i;
      var property = {type: Object, observer: updateName};
      declaration.properties[propName] = property;
      declaration[updateName] = argsUpdateFunction(i);
    }
    Polymer(declaration);
  </script>
</dom-module>
